﻿using System;
using System.Threading;

namespace UtilZ.Dotnet.Ex.Base
{
    /// <summary>
    /// 雪花算法ID生成器
    /// </summary>
    public class SnowflakeIdGenerator
    {
        //高位->低位
        //1位0   41位时间戳 10位工作机器ID    12位序列号

        private readonly ITimestampFactory _timestampFactory;
        private readonly long _machineId;

        private long _sequence = 0L;
        private long _lastTimestamp = -1L;

        private readonly int _timestampMoveBits;
        private readonly long _sequenceLogicValue;



        /// <summary>
        /// 构造函数初始化
        /// </summary>
        /// <param name="refTime">参照时间</param>
        /// <param name="machineId">工作机器ID</param>
        /// <param name="timestampBits">时间戳bit位数</param>
        /// <param name="machineIdBits">工作机器ID bit位数</param>
        /// <param name="sequenceBits">序列号bit位数</param>
        /// <exception cref="ArgumentException">数据位数数错误不等于63异常</exception>
        public SnowflakeIdGenerator(DateTime refTime, long machineId, int timestampBits = 41, int machineIdBits = 10, int sequenceBits = 12)
        {
            if (timestampBits + machineIdBits + sequenceBits != 63)
            {
                throw new ArgumentException("数据总位数必须为63位");
            }

            this._timestampFactory = new TicksTimestampFactory(refTime);
            this._machineId = machineId << (64 - machineIdBits) >> (64 - (machineIdBits + sequenceBits));
            this._timestampMoveBits = 64 - timestampBits;

            var sequenceLogicValueBuffer = new byte[sequenceBits];
            for (int i = 0; i < sequenceLogicValueBuffer.Length; i++)
            {
                sequenceLogicValueBuffer[i] = 1;
            }
            this._sequenceLogicValue = ConvertEx.BitsToInt64(sequenceLogicValueBuffer, ByteSequence.LittleEndian);
        }

        /// <summary>
        /// 生成下一个ID
        /// </summary>
        /// <returns>下一个ID</returns>
        public long Next()
        {
            long timestamp = this._timestampFactory.GetTimestamp();
            if (this._lastTimestamp > timestamp)
            {
                Interlocked.Exchange(ref this._sequence, 0L);
                this._lastTimestamp = timestamp;
            }

            return ((timestamp << this._timestampMoveBits) >> 1) | this._machineId | (this._sequenceLogicValue & Interlocked.Increment(ref this._sequence));
        }

    }
}
